using System;
using System.Configuration;
using System.Collections.Generic;
using System.Xml;
using Guadagno.Utilities;

using Schedule;

namespace TaskPluginInterface
{

    /// <summary>
    /// Helper functions for reading the Schedule from the Configuration file and
    /// parsing it into its correct IScheduleItem
    /// </summary>
    public static class ScheduleConfig
    {
        
        #region NodeNames Class

        /// <summary>
        /// Contains a list of all of the node names that the schedule config can contain.
        /// </summary>
        public static class NodeNames
        {
            /// <summary>
            /// The string representative of the Schedule node.
            /// </summary>
            public const string Schedule = "Schedule";
            /// <summary>
            /// The string representative of the ScheduledTime node.
            /// </summary>
            public const string ScheduledTime = "ScheduledTime";
            /// <summary>
            /// The string representative of the SimpleInterval node.
            /// </summary>
            public const string SimpleInterval = "SimpleInterval";
            /// <summary>
            /// The string representative of the SingleEvent node.
            /// </summary>
            public const string SingleEvent = "SingleEvent";
            /// <summary>
            /// The string representative of the BlockWrapper node.
            /// </summary>
            public const string BlockWrapper = "BlockWrapper";
            /// <summary>
            /// The string representative of the EventQueue node.
            /// </summary>
            public const string EventQueue = "EventQueue";
        }
        #endregion NodeNames Class

        #region NodeAttributes Class
        /// <summary>
        /// Represents the Node Attributes that are available
        /// </summary>
        public static class NodeAttributes
        {
            /// <summary>
            /// The string representative of the Base attribute.
            /// </summary>
            public const string Base = "Base";
            /// <summary>
            /// The string representative of the Offset attribute.
            /// </summary>
            public const string Offset = "Offset";
            /// <summary>
            /// The string representative of the StartTime attribute.
            /// </summary>
            public const string StartTime = "StartTime";
            /// <summary>
            /// The string representative of the Interval attribute.
            /// </summary>
            public const string Interval = "Interval";
            /// <summary>
            /// The string representative of the EndTime attribute.
            /// </summary>
            public const string EndTime = "EndTime";
            /// <summary>
            /// The string representative of the Count attribute.
            /// </summary>
            public const string Count = "Count";
            /// <summary>
            /// The string representative of the EventTime attribute.
            /// </summary>
            public const string EventTime = "EventTime";
            /// <summary>
            /// The string representative of the IScheduleItem attribute.
            /// </summary>
            public const string IScheduleItem = "IScheduleItem";
            /// <summary>
            /// The string representative of the BeginOffset attribute.
            /// </summary>
            public const string BeginOffset = "BeginOffset";
            /// <summary>
            /// The string representative of the EndOffset attribute.
            /// </summary>
            public const string EndOffset = "EndOffset";

        }
        #endregion NodeAttributes Class

        #region Private Variables

        private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        #endregion Private Variables

        #region Public Constants

        #endregion Public Constants

        #region Load
        /// <summary>
        /// Loads the schedule configuration from the passed XML configuration section
        /// </summary>
        /// <param name="configSection">A configuration section to parse </param>
        /// <returns>A List of <see cref="IScheduledItem"/> items</returns>
        /// <exception cref="ConfigurationErrorsException" />
        public static List<IScheduledItem> Load(string configSection)
        {
            _log.Debug(Properties.Resources.Msg_ScheduleConfig_Load);
            
            List<IScheduledItem> listSchedule = new List<IScheduledItem>();

            if (string.IsNullOrEmpty(configSection)) return listSchedule;

            try
            {
                XmlDocument xml = new XmlDocument();
                xml.LoadXml(configSection);

                _log.Debug(Properties.Resources.Msg_ScheduleConfig_Load_SelectNode);
                XmlNode schedule = xml.SelectSingleNode(NodeNames.Schedule);

                _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_FoundCount, schedule.ChildNodes.Count);
                foreach (XmlNode node in schedule.ChildNodes)
                {
                    _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_NodeType, node.Name);
                    switch (node.Name)
                    {
                        case NodeNames.ScheduledTime:
                            listSchedule.Add(GetScheduledTime(node));
                            break;

                        case NodeNames.SimpleInterval:
                            listSchedule.Add(GetSimpleInterval(node));
                            break;

                        case NodeNames.SingleEvent:
                            listSchedule.Add(GetSingleEvent(node));
                            break;

                        case NodeNames.BlockWrapper:
                            listSchedule.Add(GetBlockWrapper(node));
                            break;

                        case NodeNames.EventQueue:
                            listSchedule.Add(GetEventQueue(node));
                            break;

                    }
                }

            }
            catch (Exception ex)
            {
                _log.Error(Properties.Resources.Msg_ScheduleConfig_Load_Exception, ex);
                throw new ConfigurationErrorsException(Properties.Resources.Msg_ScheduleConfig_Load_Exception, ex);
            }

            return listSchedule;
        }
        #endregion Load

        #region IScheduleTime Types

        #region GetScheduledTime
        /// <summary>
        /// Reads the <see cref="XmlNode"/> and returns a <see cref="ScheduledTime"/> object.
        /// </summary>
        /// <param name="node">The <see cref="ScheduledTime"/> XML Node</param>
        /// <returns>A <see cref="ScheduledTime"/></returns>
        /// <remarks>
        /// There is one valid attribute combination available for a <see cref="ScheduledTime"/> object.<br></br>
        /// The Node must have a <b>Base</b> and <b>Offset</b> attribute.<br></br>
        /// <example>&lt;<see cref="ScheduledTime"/> Base="" Offset="" /&gt;</example>
        /// </remarks>
        public static ScheduledTime GetScheduledTime(XmlNode node)
        {
            _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_Node, NodeNames.ScheduledTime);
            
            // Options Available
            // Base, Offset

            string sBase;
            string sOffset;

            sBase = node.Attributes[NodeAttributes.Base].Value;
            sOffset = node.Attributes[NodeAttributes.Offset].Value;

            if (sBase == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.Base, NodeNames.ScheduledTime), node);
            if (sOffset == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.Offset, NodeNames.ScheduledTime), node);

            return new ScheduledTime(sBase, sOffset);
        }
        #endregion GetScheduledTime

        #region GetSimpleInterval

        /// <summary>
        /// Reads the <see cref="XmlNode"/> and returns a <see cref="SimpleInterval"/>
        /// </summary>
        /// <param name="node">The <see cref="SimpleInterval"/> <see cref="XmlNode"/> to parse.</param>
        /// <returns>A <see cref="SimpleInterval"/></returns>
        /// <remarks>
        /// There are three available combinations for the <see cref="SimpleInterval"/> node.<br></br>
        /// StartTime, Interval, EndTime<br></br>
        /// StartTime, Interval<br></br>
        /// StartTime, Interval, Count<br></br>
        /// </remarks>
        /// <example>
        /// &lt;SimpleInterval <b>StartTime</b>="" <b>Interval</b>="" <b>EndTime</b>="" /&gt;<br></br> 
        /// &lt;SimpleInterval <b>StartTime</b>="" <b>Interval</b>="" /&gt;<br></br> 
        /// &lt;SimpleInterval <b>StartTime</b>="" <b>Interval</b>="" <b>Count</b>="" /&gt;<br></br> 
        /// </example>
        public static SimpleInterval GetSimpleInterval(XmlNode node)
        {
            _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_Node, NodeNames.SimpleInterval);

            // Options Available for SimpleInterval
            // StartTime, Interval, EndTime
            // StartTime, Interval
            // StartTime, Interval, Count

            DateTime? startTime = null;
            DateTime? endTime = null;
            TimeSpan? interval = null;
            int? count = null;

            if (node.Attributes[ NodeAttributes.StartTime] != null) { TryParse.TryNullableDateTime(node.Attributes["StartTime"].Value, out startTime); }

            if (node.Attributes[ NodeAttributes.Interval] != null) { TryParse.TryNullableTimeSpan(node.Attributes["Interval"].Value, out interval); }

            if (node.Attributes[ NodeAttributes.EndTime] != null) { TryParse.TryNullableDateTime(node.Attributes["EndTime"].Value, out endTime); }

            if (node.Attributes[ NodeAttributes.Count] != null) { TryParse.TryNullableInt(node.Attributes["Count"].Value, out count); }

            // Determine which Constructor to call
            // StartTime and Interval are required

            if (startTime == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.StartTime, NodeNames.SimpleInterval), node);
            if (interval == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.Interval, NodeNames.SimpleInterval), node);

            if (endTime != null)
            {
                return new SimpleInterval(startTime.Value, interval.Value, endTime.Value);
            }
            if (count != null)
            {
                return new SimpleInterval(startTime.Value, interval.Value, count.Value);
            }

            // The default
            return new SimpleInterval(startTime.Value, interval.Value);

        }

        #endregion GetSimpleInterval

        #region GetSingleEvent
        /// <summary>
        /// Read the <see cref="XmlNode"/> and returns a <see cref="SingleEvent"/>
        /// </summary>
        /// <param name="node">The <see cref="SingleEvent"/> <see cref="XmlNode"/> to parse.</param>
        /// <returns>A <see cref="SingleEvent"/></returns>
        /// <remarks>There is one available option for the <see cref="SingleEvent"/> node.</remarks>
        /// <example>&lt;SingleEvent <b>EventTime</b>=""&gt;</example>
        public static SingleEvent GetSingleEvent(XmlNode node)
        {
            _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_Node, NodeNames.SingleEvent);

            // Options Available
            // EventTime

            DateTime? eventTime = null;

            if (node.Attributes[ NodeAttributes.EventTime] != null) { TryParse.TryNullableDateTime(node.Attributes["EventTime"].Value, out eventTime); }
            if (eventTime != null)
            {
                return new SingleEvent(eventTime.Value);
            }
            
            throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.EventTime, NodeNames.SingleEvent), node);
        }

        #endregion GetSingleEvent

        #region GetBlockWrapper
        /// <summary>
        /// Reads the <see cref="XmlNode"/> and returns a <see cref="SimpleInterval"/>
        /// </summary>
        /// <param name="node">The <see cref="BlockWrapper"/> <see cref="XmlNode"/></param>
        /// <returns>A <see cref="BlockWrapper"/></returns>
        /// <remarks>
        /// The Block wrapper requires a Base, BeginOffset and EndOffset attributes and a Child node of an <see cref="IScheduledItem"/>
        /// </remarks>
        /// <example>
        /// &lt;BlockWrapper <b>Base</b>="" <b>BeginOffset</b>="" <b>EndOffset</b>&gt;<br></br>
        ///   &lt;SimpleInterval <b>StartTime</b>="" <b>Interval</b>="" <b>EndTime</b>="" /&gt;<br></br> 
        /// &lt;BlockWrapper&gt;
        /// </example>
        public static BlockWrapper GetBlockWrapper(XmlNode node)
        {
            _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_Node, NodeNames.BlockWrapper);

            // Options Available
            // IScheduleItem, Base, BeginOffset, EndOffset

            string sBase, beginOffset, endOffset;
            IScheduledItem scheduledItem = null;

            sBase = node.Attributes[NodeAttributes.Base].Value;
            beginOffset = node.Attributes[ NodeAttributes.BeginOffset].Value;
            endOffset = node.Attributes[ NodeAttributes.EndOffset].Value;

            if (sBase == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute,  NodeAttributes.Base, NodeNames.BlockWrapper), node);
            if (beginOffset == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.BeginOffset, NodeNames.BlockWrapper), node);
            if (endOffset == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.EndOffset, NodeNames.BlockWrapper), node);

            XmlNode scheduleNode = node.FirstChild;

            if (scheduleNode == null) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.IScheduleItem, NodeNames.BlockWrapper), node);

            _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_Node, scheduleNode.Name);
            switch (scheduleNode.Name)
            {
                case NodeNames.ScheduledTime:
                    scheduledItem = GetScheduledTime(scheduleNode);
                    break;

                case NodeNames.SimpleInterval:
                    scheduledItem = GetSimpleInterval(scheduleNode);
                    break;

                case NodeNames.SingleEvent:
                    scheduledItem = GetSingleEvent(scheduleNode);
                    break;

            }

            return new BlockWrapper(scheduledItem, sBase, beginOffset, endOffset);
        }

        #endregion GetBlockWrapper

        #region GetEventQueue
        /// <summary>
        /// Reads the <see cref="XmlNode"/> and returns an <see cref="EventQueue"/>
        /// </summary>
        /// <param name="node">The <see cref="EventQueue"/> node to parse</param>
        /// <returns><see cref="EventQueue"/>An <see cref="EventQueue"/></returns>
        /// <remarks>
        /// The <see cref="EventQueue"/> contains one or more IScheduleItems
        /// </remarks>
        /// <example>
        /// &lt;EventQueue&gt; <br></br>
        ///     &lt;SimpleInterval <b>StartTime</b>="" <b>Interval</b>="" <b>EndTime</b>="" /&gt;<br></br> 
        ///     &lt;SimpleInterval <b>StartTime</b>="" <b>Interval</b>="" /&gt;<br></br> 
        ///     &lt;SimpleInterval <b>StartTime</b>="" <b>Interval</b>="" <b>Count</b>="" /&gt;<br></br> 
        /// &lt;/EventQueue&gt;
        /// </example>
        public static EventQueue GetEventQueue(XmlNode node)
        {
            _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_Node, NodeNames.EventQueue);

            EventQueue queue = new EventQueue();

            if (node.ChildNodes.Count == 0) throw new ConfigurationErrorsException(string.Format(Properties.Resources.Msg_ScheduleConfig_Ex_MissingAttribute, NodeAttributes.IScheduleItem, NodeNames.EventQueue), node);

            foreach (XmlNode child in node.ChildNodes)
            {
                _log.DebugFormat(Properties.Resources.Msg_ScheduleConfig_Load_Node, child.Name);
                switch (child.Name)
                {
                    case NodeNames.ScheduledTime:
                        queue.Add(GetScheduledTime(child));
                        break;

                    case NodeNames.SimpleInterval:
                        queue.Add(GetSimpleInterval(child));
                        break;

                    case NodeNames.SingleEvent:
                        queue.Add(GetSingleEvent(child));
                        break;

                    case NodeNames.BlockWrapper:
                        queue.Add(GetBlockWrapper(child));
                        break;

                    case NodeNames.EventQueue:
                        queue.Add(GetEventQueue(child));
                        break;
                }
            }

            return queue;
        }
        #endregion GetEventQueue

        #endregion IScheduleTime Types

        #region Helper Functions

        #endregion Helper Functions

    }
}
